import tensorflow as tf
import numpy as np

# f(x) = a*x**2 + b*x + c的导数

#定义相应的变量x以及常量a,b,c
x = tf.Variable(0.0, name="x", dtype=tf.float32)
a = tf.constant(1.0)
b = tf.constant(-2.0)
c = tf.constant(1.0)

#定义需要求导的公式：将公式放入磁带中
with tf.GradientTape() as tape:  #Gradient梯度 Tape磁带
    y = a * tf.pow(x, 2) + b * x + c  #定义求导公式, tf.pow(x,2)  x的二次方

#求导数：y对x求导
dy_dx = tape.gradient(y, x)
print(dy_dx)


# 对常量张量也可以求导，需要增加watch
with tf.GradientTape() as tape:
    tape.watch([a, b, c])   #需要对常量watch
    y = a * tf.pow(x, 2) + b * x + c

#求导数
dy_dx, dy_da, dy_db, dy_dc = tape.gradient(y, [x, a, b, c])
print(dy_da)
print(dy_dc)

# 可以求二阶导数
with tf.GradientTape() as tape2:
    with tf.GradientTape() as tape1:  #定义一阶导数
        y = a*tf.pow(x,2) + b*x + c   #指定公式
    dy_dx = tape1.gradient(y,x)   #y对x求一阶导数

dy2_dx2 = tape2.gradient(dy_dx,x) #y对x求二阶导数

print(dy2_dx2)

# 可以在autograph中使用

@tf.function  #声明定义autograph中的函数, 可以直接使用的同时，变成静态图中的函数，提高运算效率
def f(x):
    #定义常量a,b,c
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)

    # 自变量转换成tf.float32
    x = tf.cast(x, tf.float32)   # x是常量
    with tf.GradientTape() as tape:
        tape.watch(x)   #对常量x求导,必须声明watch
        y = a * tf.pow(x, 2) + b * x + c
    dy_dx = tape.gradient(y, x)

    return ((dy_dx, y))

tf.print(f(tf.constant(0.0)))
tf.print(f(tf.constant(1.0)))